import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

/**
 * The main class of this program. The program is able to recognize 4 different
 * facial expressions, Sad, Happy, Mischievous and Angry. The faces are
 * described using a pixelmap of 20x20 pixels ranging from 0-31 in black level.
 * The classification is done using a neural network of 4 output neurons in
 * which each pixel is an input.
 */
public class Faces {

    public static void main(final String[] args) {

        // Arguments must include TrainingFile, TrainingFacit and a testfile.
        if (args.length < 3) {
            System.out.println("Usage - TrainingFile TrainingFacit TestFile");
            System.exit(1);
        }
        LinkedList<PixelMapData> trainingData = null;
        HashMap<String, Double> trainingFacit = null;
        LinkedList<PixelMapData> testData = null;
        // read all the files
        try {
            trainingData = readBitMapData(args[0]);
            trainingFacit = readTrainingFacit(args[1]);
            testData = readBitMapData(args[2]);
        } catch (final IOException e) {
            e.printStackTrace();
        }
        // create our NN and train it.
        final NeuralNet ANN = new NeuralNet(400, 4);
        ANN.train(trainingData, trainingFacit);

        // finally classify the testdata.
        for (final PixelMapData bmp : testData) {
            final int answer = (int) ANN.classify(bmp.getData());
            System.out.println(bmp.getImageName() + " " + answer);
        }

    }

    /**
     * Reads facit data from the specified file. The file is expected to be in
     * the format "pixelmapName answer". To get the correct answer for a
     * pixelmap one can query the returned hashmap on the pixelmap name. Lines
     * that begin with # are ignored and so are empty ones.
     * 
     * @param filePath
     *            a path to the file from which facit data should be read.
     * @return a Hashmap in which keys are pixelmap names and values are their
     *         facit/"answer".
     * @throws IOException
     *             if there's a problem reading the file.
     */
    private static HashMap<String, Double> readTrainingFacit(
            final String filePath) throws IOException {
        final HashMap<String, Double> FacitData = new HashMap<String, Double>();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.startsWith("#") || line.trim().isEmpty()) {
                    // ignore comment/empty lines
                    continue;
                }
                final String[] data = line.split(" ");
                FacitData.put(data[0], Double.parseDouble(data[1]));
            }
        }

        return FacitData;
    }

    /**
     * Reads pixelmaps from a file. Each map should be delimited by a name. The
     * name of the pixelmap should be followed by data that describes it. Lines
     * that begin with # are ignored and so are empty ones.
     * 
     * @param fPath
     *            a path to the file from which pixelmap data should be read.
     * @return a list containing all the pixelmaps described in the file.
     * @throws IOException
     *             if there's a problem reading the file.
     */
    private static LinkedList<PixelMapData> readBitMapData(final String fPath)
            throws IOException {
        /*
         * SCALAR is used to pre-process the pixeldata since our ANN work in 0-1
         * interval.
         */
        final double SCALAR = 1.0 / 32.0;
        final LinkedList<PixelMapData> imgData = new LinkedList<PixelMapData>();
        ArrayList<ArrayList<Double>> data = null;
        String imageName = null;

        try (BufferedReader br = new BufferedReader(new FileReader(fPath))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.startsWith("#") || line.trim().isEmpty()) {
                    // ignore comment/empty lines
                    continue;
                }
                if (line.startsWith("Image")) {
                    // str is name of following image data.
                    if (data != null && imageName != null) {
                        imgData.add(new PixelMapData(data, imageName));
                    }
                    imageName = line;
                    data = new ArrayList<ArrayList<Double>>(20);
                } else {
                    // str contains pixel row data
                    final String[] rowData = line.split(" ");
                    final ArrayList<Double> row = new ArrayList<Double>(20);
                    for (final String i : rowData) {
                        row.add(Double.parseDouble(i) * SCALAR);
                    }
                    data.add(row);
                }
            }
            /*
             * Must add the last image since the file does not end with a new
             * image name.
             */
            if (data != null && imageName != null) {
                imgData.add(new PixelMapData(data, imageName));
            }
        }

        return imgData;
    }
}
